前幾天我們把系統從單一 Server 擴展成:
┌── Server #1
│
User → Load Balancer ├── Server #2
│
└── Server #3
↓
Database
Backend 可以 Horizontal Scaling,但所有 Server 最後仍然需要存取資料。
例如:
Customer
Product
Shopping Cart
Order
Payment
這時候就會遇到另一個重要問題:
這些資料到底應該怎麼存?
最常看到的兩個方向就是:
SQL
vs
NoSQL
今天除了 SQL / NoSQL,也會加入一組很適合台積電 IT Software Engineer 面試準備的情境:
設計 Customer、Product、Shopping Cart 與 Checkout。
SQL Database 通常指 Relational Database。
常見例子:
PostgreSQL
MySQL
MariaDB
Oracle
SQL Server
Relational Database 的核心特色之一是資料以 Table 組織,而且不同 Table 可以建立 Relationship。
例如:
users
| id | name | |
|---|---|---|
| 1 | Alvin | alvin@example.com |
| 2 | Bob | bob@example.com |
另一張 Table:
orders
| id | user_id | total_amount |
|---|---|---|
| 101 | 1 | 1200 |
| 102 | 1 | 800 |
其中:
orders.user_id → users.id
形成:
User
│
└── has many → Orders
例如:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255)
);
id 是 Primary Key,用來唯一識別 User。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
total_amount DECIMAL(10, 2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
user_id 則是 Foreign Key。
因此 SQL Database 很適合處理具有明確 Relationship 的資料。
假設想取得 Alvin 的 Orders:
SELECT
users.name,
orders.id,
orders.total_amount
FROM users
JOIN orders
ON users.id = orders.user_id
WHERE users.id = 1;
Relationship 可能一路延伸:
Users
↓
Orders
↓
Order Items
↓
Products
這種 Data Model 在 E-commerce、Banking、ERP、Inventory System 都非常常見。
NoSQL 並不是單一種類的 Database。
常見 Data Model:
Document
Key-Value
Wide-Column
Graph
例如:
MongoDB → Document
Redis → Key-Value / Data Structure Store
Cassandra → Wide-Column
Neo4j → Graph
以 Document Database 為例:
{
"_id": 1,
"name": "Alvin",
"email": "alvin@example.com",
"preferences": {
"language": "zh-TW",
"theme": "dark"
}
}
Document Model 在某些資料結構經常變動、欄位差異大的情境下會比較自然。
| SQL | NoSQL | |
|---|---|---|
| Data Model | Table / Row | Document、Key-Value 等 |
| Schema | 通常較明確 | 依 Database 類型而定 |
| Relationship | 很擅長 | 視資料模型而定 |
| JOIN | 常見 | 不一定支援或不鼓勵大量 JOIN |
| Transaction | 成熟且常見 | 能力依 Database 而不同 |
| Scaling | Vertical / Horizontal 都可 | 很多產品強調 Distributed Scaling |
但不要把它背成:
SQL = 小系統
NoSQL = 大系統
大型系統同樣可以使用 SQL。
SQL Database 也能搭配:
Index
Read Replica
Partitioning
Sharding
Caching
處理大量資料與 Traffic。
所以 Database Selection 應該考慮:
Data Model?
Relationship?
Query Pattern?
Read / Write Ratio?
Transaction Requirement?
Consistency Requirement?
Expected Scale?
假設面試官說:
請設計一個 Shopping System Database,包含 Customer、Product 和 Shopping Cart。
不要馬上畫 Table。
先確認 Requirement:
一個 Customer 可以有幾個 Active Cart?
一個 Cart 可以有多少 Product?
同一 Product 可以出現在不同 Cart 嗎?
需要 Quantity 嗎?
Product Price 會改變嗎?
Checkout 後 Cart 怎麼處理?
System Design 很重要的習慣:
Requirement → Design
CREATE TABLE customers (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL
);
CREATE TABLE carts (
id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
例如:
ACTIVE
CHECKED_OUT
一個 Cart 可以有很多 Product,一個 Product 也可以出現在很多 Cart。
這是:
Many-to-Many Relationship
所以建立中間 Table:
CREATE TABLE cart_items (
cart_id BIGINT,
product_id BIGINT,
quantity INT NOT NULL,
PRIMARY KEY (cart_id, product_id),
FOREIGN KEY (cart_id) REFERENCES carts(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
Relationship:
Customer
↓
Cart
↓
Cart Items
↓
Product
User 按下 Checkout 時可能需要:
1. 確認 Cart
2. 確認 Inventory
3. 建立 Order
4. 建立 Order Items
5. 扣除 Inventory
6. 更新 Cart Status
概念上:
BEGIN TRANSACTION
Check Inventory
Create Order
Create Order Items
Update Inventory
Update Cart
COMMIT
如果:
Create Order ✅
Update Inventory ❌
我們不希望留下半完成的資料。
這就需要:
Transaction
Transaction(交易):把多個 Database Operations
當成「同一件事情」一起處理。
例如購物網站 Checkout:
1. 檢查 Inventory
2. 扣除 Product Stock
3. 建立 Order
4. 建立 Payment Record
我們不希望出現「Stock 扣掉了,但 Order
沒建立成功」這種只完成一半的狀態。
COMMIT(提交):Transaction 全部成功後,正式確認這次修改。
ROLLBACK(回滾):Transaction 發生重要錯誤時,取消這次 Transaction
的修改。
全部成功 → COMMIT
任何重要步驟失敗 → ROLLBACK
ACID 是 Database Transaction 的四個重要 Properties:
A → Atomicity → 原子性
C → Consistency → 一致性
I → Isolation → 隔離性
D → Durability → 持久性
最簡單可以記成:
Atomicity 原子性
→ 不要只做一半
Consistency 一致性
→ 不要破壞資料規則
Isolation 隔離性
→ 多個 Transaction 同時執行時,不要互相干擾出錯
Durability 持久性
→ Commit 成功後,資料不要消失
Atomicity 可以理解成 All or Nothing:
一組 Transaction 要嘛全部成功,要嘛全部失敗。
例如:
Step 1:Stock 1 → 0 ✅
Step 2:Create Order ❌
如果沒有 Atomicity,可能留下:
Stock = 0
但是 Order 不存在
有 Transaction 時:
Step 1 ✅
Step 2 ❌
↓
ROLLBACK
↓
取消這次 Transaction 的修改
銀行轉帳也是很好的例子:
Alvin -$100
Bob +$100
不能只發生:
Alvin -$100 ✅
Bob +$100 ❌
因此:
Atomicity
=
All or Nothing
=
不要只做一半
Consistency:
Transaction 執行前與執行後,Database 都應維持系統定義的有效規則與
Constraints。
Constraint(限制條件):Database Data 必須遵守的規則。
例如:
stock >= 0
代表:
stock = 5 ✅
stock = 0 ✅
stock = -1 ❌
如果 Product A 只有 1 個,User 卻要買 2 個:
1 - 2 = -1
如果 Business Rule 規定 stock >= 0,Transaction 就不應讓 Database 留下stock = -1。
另一個例子:
email 必須 Unique
如果已經有:
User A → alvin@gmail.com
在有 Unique Constraint 的情況下,就不能再建立另一筆相同 Email。
簡單記:
Consistency
=
Follow the Rules
=
不要破壞資料規則
Isolation 主要處理多個 Transactions 同時操作 Data 時可能發生的問題。
假設:
Product A
Stock = 1
Alvin 和 Bob 幾乎同時 Checkout:
Alvin Transaction ─┐
├── Product A
Bob Transaction ───┘
兩個人可能都讀到:
stock = 1
所以兩個人都以為自己買得到,最後可能:
Alvin Order ✅
Bob Order ✅
但實際只有一件商品。
這叫 Overselling(超賣):賣出去的數量超過真正可用的 Inventory。
Concurrent Transactions(並發交易):多個 Transactions
在重疊的時間內進行。
Time ─────────────────────────→
Alvin: [ Read Stock ][ Create Order ]
Bob: [ Read Stock ][ Create Order ]
Isolation(隔離性):
Database 要控制同時執行的 Transactions
如何彼此看到資料,避免互相干擾造成錯誤結果。
實際 Database 可能使用:
Lock
Isolation Level
Optimistic Locking
Pessimistic Locking
Atomic Update
初學階段先記:
Isolation
=
Don't Interfere
=
多個 Transaction 同時執行時
不要互相干擾到產生錯誤結果
Durability:
Transaction 一旦成功 COMMIT,結果就應該被可靠保存。
例如:
Alvin Checkout
↓
Create Order
↓
COMMIT ✅
↓
Server Crash
重新啟動後,已經 Commit 的 Order 不應隨意消失。
Persistent Storage(持久化儲存):Machine 重開機後,Data
仍可以保留下來的 Storage,例如 Disk / SSD。
Transaction Log(交易日誌):Database 用來記錄 Data Changes 的
Log,可協助 Crash 後進行 Recovery。
Recovery(恢復):System 發生 Failure
後,重新恢復到可以正確工作的狀態。
初學階段先記:
COMMIT
↓
Database 說成功
↓
Server Crash
↓
成功的資料應該仍然可以被恢復
因此:
Durability
=
Don't Lose Committed Data
=
Commit 成功後資料不要消失
假設:
Product Stock = 1
Checkout 需要:
1. 扣 Stock
2. 建立 Order
3. 建立 Payment Record
不要只成功一半
重要步驟失敗就 Rollback。
不要違反 Database / Business Rules
例如不能留下:
stock = -1
多個 Transactions 同時執行時
不要互相干擾而產生錯誤結果
例如避免兩個人同時買到最後一件商品。
成功 Commit 的資料不能隨意消失
即使 Server Crash,已成功的 Order 仍應被可靠保存或恢復。
A → Atomicity(原子性)
All or Nothing
不要只做一半
C → Consistency(一致性)
Follow the Rules
不要破壞資料規則
I → Isolation(隔離性)
Don't Interfere
多個 Transaction 同時執行時不要互相干擾出錯
D → Durability(持久性)
Don't Lose Committed Data
Commit 成功後資料不要消失
或者:
A 原子性
→ 做一半?不行。
C 一致性
→ 違反資料規則?不行。
I 隔離性
→ 同時操作造成錯誤?要處理。
D 持久性
→ 都說 Commit 成功了,資料卻消失?不行。
ACID 的核心目的,就是讓 Database Transaction
即使遇到錯誤、多人同時操作或 System
Failure,仍然盡可能維持正確而可靠的 Data。
這是一個很值得練習的問題:
Product 只剩最後一個,兩個 Customer 同時 Checkout 怎麼辦?
不能只寫:
if stock > 0:
stock -= 1
因為可能發生:
Initial Stock = 1
Transaction A reads 1
Transaction B reads 1
A → Buy
B → Buy
這就是:
Race Condition / Concurrency Problem
可能需要考慮:
Database Lock
Atomic Update
Optimistic Locking
Pessimistic Locking
Isolation Level
例如概念上可以:
UPDATE products
SET stock = stock - 1
WHERE id = 101
AND stock > 0;
再檢查:
Affected Rows = 1 → 成功
Affected Rows = 0 → 沒有庫存
這可以避免某些:
SELECT stock
↓
Application 判斷
↓
UPDATE stock
產生的 Read-Modify-Write Race Condition。
一個 Checkout 可以一路延伸:
Database Schema
↓
Relationship
↓
Transaction
↓
ACID
↓
Concurrency
↓
Isolation
↓
Locking
↓
High Traffic
↓
Database Bottleneck
真正需要練習的不是只背:
SQL = Relational
NoSQL = Non-relational
而是:
從 Requirement 一步一步發現系統問題,並解釋自己的 Solution 與 Trade-off。
例如不同 Product 的 Metadata 差異很大:
{
"type": "laptop",
"cpu": "M3",
"ram": "16GB"
}
另一種商品:
{
"type": "shirt",
"size": "L",
"material": "cotton"
}
Document Database 在這類彈性 Data Model 中可能比較自然。
另外:
session:abc123
↓
User Session
這種 Key → Value Access Pattern 則可能適合 Key-Value Store。
所以不是:
哪個 Database 比較強?
而是:
哪個 Data Model 最符合 Requirement 與 Access Pattern?
可以。
例如:
Orders
Payments
Inventory
↓
PostgreSQL
因為重視:
Transaction
Relationship
Consistency
而:
Session
Cache
↓
Redis
或:
Flexible Product Metadata
↓
Document Database
大型系統可能根據不同 Requirement 使用不同 Storage Technology。
如果面試官問:
你會選 SQL 還是 NoSQL?
先思考:
Data Model?
Relationship?
Query Pattern?
Read / Write Ratio?
Transaction Requirement?
Consistency Requirement?
Expected Scale?
然後再說明:
Based on these requirements,
I would start with PostgreSQL because...
重點不是猜面試官想聽哪個 Database。
而是:
說明你的選擇,以及 Trade-off。
仍然回到這個 Framework:
Problem
↓
Requirement
↓
Solution
↓
Trade-off
今天可以直接練習:
1. 請設計 Customer、Product、Shopping Cart 的 Database Schema。
2. Customer 和 Cart 是什麼 Relationship?
3. Cart 和 Product 是什麼 Relationship?
4. 為什麼需要 cart_items?
5. Checkout 時需要哪些 Database Operations?
6. 什麼是 Transaction?
7. ACID 分別代表什麼?
8. Checkout 中途失敗怎麼辦?
9. 只剩最後一個 Product,
兩個 User 同時 Checkout 怎麼辦?
10. Isolation 解決什麼問題?
11. 這個系統會選 SQL 還是 NoSQL?
Why?
12. Traffic 增加後 Database 變成 Bottleneck,
下一步會怎麼處理?
如果能不看答案,用自己的話完整講出這些問題,今天就同時完成:
鐵人賽 Day 5
+
System Design 基礎
+
台積電 IT 面試複習
SQL 很適合處理:
Structured Data
Relationship
JOIN
Transaction
Data Integrity
NoSQL 則包含:
Document
Key-Value
Wide-Column
Graph
選擇 Database 時,不要只看資料量。
應該從:
Requirement
Data Model
Query Pattern
Transaction
Consistency
Scale
一起考慮。
另外,Shopping Cart / Checkout 讓我們開始接觸:
Transaction
ACID
Concurrency
Isolation
Race Condition
這些觀念之後還會繼續出現在更完整的 System Design 裡。
當資料量從:
1,000 rows
↓
1,000,000 rows
↓
100,000,000 rows
Query 可能開始變慢。
例如:
SELECT *
FROM users
WHERE email = 'alvin@example.com';
Database 要怎麼快速找到資料?
下一篇:
Day 6|Database Index:為什麼加一個 Index,Query 可以快這麼多?
會開始了解:
Full Table Scan
Index
B-Tree
Read Performance
Write Cost
Composite Index
並延伸:
為什麼 Database Query 很慢?
怎麼找 Database Bottleneck?
什麼情況應該加 Index?
為什麼不能每個 Column 都加 Index?
再一步一步進入:
Database Optimization
Cache
Read Replica
Sharding